package com.icesoft.core.web.configurer.filter;

import com.icesoft.core.common.helper.AnnotationUtil;
import com.icesoft.core.web.base.BaseAnnotationInterceptor;
import com.icesoft.core.web.base.BaseHandlerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.BiFunction;

@Component
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@SuppressWarnings({"rawtypes", "unchecked"})
@Slf4j
public class WebRequestAnnotationInterceptor extends BaseHandlerInterceptor {

    @Override
    public String pathPatten() {
        return "/**";
    }

    @Autowired(required = false)
    List<BaseAnnotationInterceptor> interceptors;

    private static final String GLOBE_REQUEST_START_TIME = "GLOBE_REQUEST_START_TIME";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (log.isTraceEnabled()) {
            request.setAttribute(GLOBE_REQUEST_START_TIME, System.currentTimeMillis());
            log.trace("url:{}", request.getRequestURI());
        }
        if (!(handler instanceof HandlerMethod) || interceptors == null) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if (!forEach(handlerMethod, (interceptor, annotation) -> {
            if (!interceptor.preHandle(request, response, handlerMethod, annotation)) {
                if (log.isDebugEnabled()) {
                    log.debug("请求{}被拦截，class={}", request.getRequestURI(), interceptor.getClass().getName());
                }
                return false;
            }
            return true;
        })) {
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) {
        if (log.isTraceEnabled()) {
            Long start = (Long) request.getAttribute(GLOBE_REQUEST_START_TIME);
            if (start == null) {
                log.warn("未找到开始时间 url:{}", request.getRequestURI());
            } else {
                log.trace("postHandle url:{},请求用时：{} ms", request.getRequestURI(),
                        System.currentTimeMillis() - start);
            }
        }
        if (!(handler instanceof HandlerMethod) || interceptors == null) {
            return;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        forEach(handlerMethod, (interceptor, annotation) -> {
            interceptor.postHandle(request, response, handlerMethod, modelAndView, annotation);
            return true;
        });
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (log.isTraceEnabled()) {
            Long start = (Long) request.getAttribute(GLOBE_REQUEST_START_TIME);
            if (start == null) {
                log.warn("未找到开始时间 url:{}", request.getRequestURI());
            } else {
                log.trace("afterCompletion url:{},请求用时：{} ms", request.getRequestURI(),
                        System.currentTimeMillis() - start);
            }
        }
        if (!(handler instanceof HandlerMethod) || interceptors == null) {
            return;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        forEach(handlerMethod, (interceptor, annotation) -> {
            interceptor.afterCompletion(request, response, handlerMethod, ex, annotation);
            return true;
        });
    }

    private static Map<Method, Map<Class, Annotation>> methodInterceptorMap = new WeakHashMap<>(1000);

    private boolean forEach(HandlerMethod handlerMethod,
                            BiFunction<BaseAnnotationInterceptor, Annotation, Boolean> biFunction) {
        Map<Class, Annotation> map = methodInterceptorMap.get(handlerMethod.getMethod());
        if (map == null) {
            map = initMethodInterceptorMap(handlerMethod.getMethod());
        }
        for (BaseAnnotationInterceptor interceptor : interceptors) {
            Annotation annotation = map.get(interceptor.getClass());
            if (annotation != null) {
                if (!biFunction.apply(interceptor, annotation)) {
                    return false;
                }
            }
        }
        return true;
    }

    private synchronized Map<Class, Annotation> initMethodInterceptorMap(Method method) {
        Map<Class, Annotation> map = methodInterceptorMap.get(method);
        if (map != null) {
            return map;
        }
        map = new HashMap<>();
        for (BaseAnnotationInterceptor interceptor : interceptors) {
            Annotation annotation = AnnotationUtil.findAnyAnnotation(method, interceptor.getAnnotationClass());
            if (annotation != null) {
                map.put(interceptor.getClass(), annotation);
            }
        }
        methodInterceptorMap.put(method, map);
        return map;
    }

}
